﻿using System;
using System.Collections.Generic;
using System.Text;

using System.Runtime.InteropServices;
using System.Configuration;

namespace Intergr8.Data
{
    public class SecuredDataProvider : DataProvider
    {

        #region Constants
        private const string CONFIGERR_CREDENTIALS_EMPTY = "SecuredDataProvider: Impersonate has been specified but the Credential detail is missing username: {0}, password: {1}, domain: {2}. {3}";

        #endregion

        #region Credentials
        /// <summary>
        /// Domain to use for impersonation access
        /// </summary>
        protected string _domain = "";

        /// <summary>
        /// Username to use for impersonation access
        /// </summary>
        protected string _username = "";

        /// <summary>
        /// password to use for impersonation access
        /// </summary>
        protected string _password = "";

        #endregion

        #region Context Switching
        /// <summary>
        /// Flag whether to use the current identity or impersonate
        /// </summary>
        protected bool _impersonate = false;

        /// <summary>
        /// Store the current identity in case we need to switch context
        /// </summary>
        private System.Security.Principal.WindowsIdentity _currentIdentity;

        /// <summary>
        /// Current context we are running under
        /// </summary>
        private System.Security.Principal.WindowsImpersonationContext _ctx;

        // Pointer to a logged on user
        IntPtr _userTokenptr;

        #endregion

        #region Initialisation
        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <param name="config"></param>
        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            try
            {
                System.Diagnostics.Debug.WriteLine("SecuredDataProvider.Initialize, name: " + name);

                // Initialise the base
                base.Initialize(name, config);

                // Pull own data from the configuration section
                if (config["Impersonate"] != null)
                {
                    this._impersonate = bool.Parse(config["Impersonate"]);
                }

                if (this._impersonate == true)
                {
                    // Must have all other valid attributes
                    try
                    {
                        // Get the credentail information
                        this._domain = config["Domain"];

                        this._password = config["Password"];

                        this._username = config["Username"];

                        if (string.IsNullOrEmpty(this._domain) ||
                            string.IsNullOrEmpty(this._password) ||
                            string.IsNullOrEmpty(this._username))
                        {
                            throw new ConfigurationErrorsException(String.Format(CONFIGERR_CREDENTIALS_EMPTY, this._username, this._password, this._domain));
                        }
                    }
                    catch (ConfigurationErrorsException cex)
                    {
                        throw cex;
                    }
                    catch (Exception ex)
                    {
                        throw new ConfigurationErrorsException(String.Format(CONFIGERR_CREDENTIALS_EMPTY, this._username, this._password, this._domain, ex.Message));
                    }
                }
                else
                {
                    // Dont need to check other attributes
                }

                System.Diagnostics.Debug.WriteLine("Impersonate = " + this._impersonate);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(String.Format("{0},{1}", "Execption occurred during Initialisation", ex.Message));
            }
            finally
            {
            }
        }
        #endregion

        #region Stub Implementation
        public override System.Data.DataTable Fetch()
        {
            throw new NotImplementedException();
        }
        #endregion

        #region P/Inkove declarations
        [DllImport("advapi32.dll", SetLastError = true)]
        public static extern bool LogonUser(
            string lpszUsername,
            string lpszDomain,
            string lpszPassword,
            int dwLogonType,
            int dwLogonProvider,
            out IntPtr phToken
            );

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool CloseHandle(IntPtr handle);

        #endregion

        #region ContextSwitching 
        /// <summary>
        /// Set up a impersonation context
        /// </summary>
        protected void SetContext()
        {
            try
            {
                if (this._impersonate == false)
                {
                    // Nothing changes
                    System.Diagnostics.Trace.WriteLine("SetContext, using current identity");
                }
                else
                {
                    System.Diagnostics.Trace.WriteLine("SetContext, reverting to configured identity");

                    // Call LogonUser to get a token for the user
                    _userTokenptr = IntPtr.Zero;

                    System.Diagnostics.Trace.WriteLine(String.Format(@"Logon User {0}\{1}", this._domain, this._username));
                    bool loggedOn = LogonUser(
                        this._username,
                        this._domain,
                        this._password,
                        2,      //LogonType.Interactive,
                        0,      //LogonProvider.Default,
                        out _userTokenptr);
                    if (!loggedOn)
                    {
                        throw new Exception("Win32 Exception: " + Marshal.GetLastWin32Error().ToString());
                    }

                    System.Diagnostics.Trace.WriteLine("User logged on successfully");

                    // Store the current user context
                    _currentIdentity = System.Security.Principal.WindowsIdentity.GetCurrent();

                    // Begin impersonation
                    _ctx = System.Security.Principal.WindowsIdentity.Impersonate(_userTokenptr);
                    System.Diagnostics.Trace.WriteLine("Impersonation complete");
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.Write("SetContext exception", ex.Message);
                CloseHandle(_userTokenptr);
                if (_ctx != null)
                {
                    _ctx.Undo();
                }
                throw ex;
            }

        }

        /// <summary>
        /// Revert back to our original context
        /// </summary>
        protected void ResetContext()
        {
            if (this._impersonate == false)
            {
                // Nothing changes
                System.Diagnostics.Trace.WriteLine("ResetContext, using current identity");
            }
            else
            {
                System.Diagnostics.Trace.WriteLine("ResetContext, switching back to original identity");
                CloseHandle(_userTokenptr);
                if (_ctx != null)
                {
                    _ctx.Undo();
                }
                System.Diagnostics.Trace.WriteLine("ResetContext, reset complete");
            }
        }
        #endregion
    }
}
